package 网络编程2套接字.回显程序;
//基于UDP的socket来写一个最简单的回显程序.回显客户端+回显服务器
//回显:Echo
//A给B说啥,B就回啥
//回显程序本身没啥意义,但是当前通过这个程序主要是为了能够熟悉Socket API具体的使用

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * 正常客户端/服务器的的通信流程:
 *   客户端            服务器
 *
 * 1.构造请求
 *   并发送
 *               2.读取请求并解析
 *               3.根据请求计算响应
 *               4.构造响应并返回
 * 5.获取到响应并显式
 */

//服务器
public class UdpEchoServer{
    private DatagramSocket socket = null;

    //port表示端口号
    //服务器在启动的时候,需要关联(绑定)上一个端口号.
    //收到数据的时候,就会根据这个端口号来决定把这个数据交给哪个进程.
    //虽然此处poet写的类型是int,但是实际上端口号是一个两个字节的无符号整数.
    //范围 0-65535
    public UdpEchoServer(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }

    //通过这个方法来启动服务器.
    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器一般都是持续运行的(7*24)
        while(true){
            //1.读取请求.当前服务器不知道客户端啥时候发来请求.receive方法也会阻塞.
            //如果真的有请求过来了,此时receive就会返回.
            //参数DatagramPacket是一个输出型参数.socket中读到的数据就会设置到这个参数的对象中.
            // DatagramPacket 在构造的时候,需要指定一个缓冲区.(就是一段内存空间,通常使用byte[])
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            /**
             * 在收到请求之前,receive方法就会堵塞
             */
            socket.receive(requestPacket);//receive得到的结果并没有作为一个返回值,而是作为一个输出型参数
            //这种操作在Java中是相对少见的.
            //Java中大部分情况下都是用返回值表示输出,用参数表示输入.很少会用参数表示输出.(引用类型的参数,也可以用来表示输出)

            //之所以这里要这么做,主要就是为了能够让用户给DatagramPacket指定一个缓冲区
            //为啥飞的要用户来指定这个缓冲区?
            //缓冲区大小其实不好确定.最好还是由程序员来确定.
            //而如果是DatagramPacket自己随意搞了一个缓冲区,搞的短了或者长了都不合适.

            //把requsetPacket对象里面的内容取出来,作为一分字符串.
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());

            //2.根据请求来计算响应
            String response = process(request);

            //3.把回响写回到客户端.这时候也需要构造一个DatagramPacket
            //此处给DatagramPacket中设置的长度,必须是"字节的个数"/
            //如果直接取response.length() 此处得到的是,字符串的长度,也就是"字符的个数"
            //当前的responsePacket在构造的时候,还需要指定这个包要发给谁
            //其实发送给的目标,就是发请求的那一方
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),request.getBytes().length,
                    requestPacket.getSocketAddress());
            socket.send(requestPacket);

            //4.加上日志打印.
            //%d表示要打印一个有符号十进制的整数.%s表示要打印一个字符串
            String log = String.format("[%s:%d] req:%s;resp:%s",
                    requestPacket.getAddress().toString(),
                    requestPacket.getPort(),
                    request,response);//格式化构造字符串的方式,类似于C语言中的sprintf
            System.out.println(log);

            //也可以使用+字符串拼接,只不过写起来会比较麻烦,不建议使用
//            String log = "[" + requestPacket.getAddress().toString() +":"+requestPacket.getPort() +"]"...
            //格式化字符串有个最大的问题:
            //格式化字符串和后面的参数必须要类型和个数都一致
            //后续的编程语言,都在逐渐摒弃格式化字符串
        }
    }
    //此处的process方法负责的功能,就是根据请求来计算响应.
    //由于当前是一个回显服务器,就把客户端发的请求直接返回回去即可.
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

